package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/nsqio/go-nsq"
	"io"
	"io/fs"
	"log"
	"os"
	"os/signal"
	"path"
	"path/filepath"
	"strings"
	"syscall"
)

var hostSub string
var hostPub string
var topicPub string
var topicSub string
var channelSubPub string
var dirs string
var nodirs string
var safe string

type myFileSyncMessageHandler struct {
	Handler func(content []byte) error
}

// HandleMessage implements the Handler interface.
func (h *myFileSyncMessageHandler) HandleMessage(m *nsq.Message) error {
	if len(m.Body) == 0 {
		// Returning nil will automatically send a FIN command to NSQ to mark the message as processed.
		// In this case, a message with an empty body is simply ignored/discarded.
		return nil
	}

	// do whatever actual message processing is desired
	err := h.Handler(m.Body)

	// Returning a non-nil error will automatically send a REQ command to NSQ to re-queue the message.
	return err
}

var fileProducer *nsq.Producer

var sigChan chan os.Signal

func main() {
	flag.StringVar(&hostSub, "sub", "127.0.0.1:4161", "用于监听的地址端口，默认从LookUp监听")
	flag.StringVar(&hostPub, "pub", "127.0.0.1:4150", "推送地址端口")
	flag.StringVar(&topicPub, "tp", "EZ.FileSync.Pub", "请求监听频道:TopicPub")
	flag.StringVar(&topicSub, "ts", "EZ.FileSync.Sub", "接受监听频道:TopicSub")
	flag.StringVar(&channelSubPub, "channel", "default", "监听频道")
	flag.StringVar(&channelSubPub, "c", "default", "监听频道")
	flag.StringVar(&dirs, "dirs", "", "扫描目录，如果为空则是监听行为，如果不为空，则是获取行为")
	flag.StringVar(&nodirs, "nodirs", "var,test,static,.idea,.git", "排除目录")
	flag.StringVar(&safe, "safe", "1", "1=安全模式，文件放到var目录下自行拷贝,0=危险模式：直接同目录覆盖")
	flag.Parse()

	//初始化一个生产者
	fileProducer = createProducer4SendFile()
	if dirs == "" {
		startListenFileNeed()
	} else {
		dirSlice := strings.Split(dirs, ",")
		p := params{
			Dirs: dirSlice,
		}
		data, err := json.Marshal(p)
		if err != nil {
			println(err)
		}

		err = fileProducer.Publish(topicPub, data)
		if err != nil {
			println(err)
		}
		startListenFileFeed()
	}

}

func startListenFileNeed() {
	config := nsq.NewConfig()
	consumer, err := nsq.NewConsumer(topicPub, channelSubPub, config)
	if err != nil {
		println(err)
	}
	// Set the Handler for messages received by this Consumer. Can be called multiple times.
	// See also AddConcurrentHandlers.
	consumer.AddHandler(&myFileSyncMessageHandler{
		Handler: func(content []byte) error {
			//收到消息后发送相应文件夹下的内容
			sendFiles(content)
			//println(string(content))
			return nil
		},
	})

	// Use nsqlookupd to discover nsqd instances.
	// See also ConnectToNSQD, ConnectToNSQDs, ConnectToNSQLookupds.
	err = consumer.ConnectToNSQLookupd(hostSub)
	if err != nil {
		println(err)
	}

	//wait for signal to exit
	sigChan = make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
	<-sigChan

	//Gracefully stop the consumer.
	consumer.Stop()
}

func startListenFileFeed() {
	config := nsq.NewConfig()
	consumer, err := nsq.NewConsumer(topicSub, channelSubPub, config)
	if err != nil {
		println(err)
	}
	// Set the Handler for messages received by this Consumer. Can be called multiple times.
	// See also AddConcurrentHandlers.
	consumer.AddHandler(&myFileSyncMessageHandler{
		Handler: func(content []byte) error {
			if string(content) == "EZFPS_DONE" {
				sigChan <- syscall.SIGTERM
			} else {
				//收到消息后发送相应文件夹下的内容
				storeFiles(content)
				//println(string(content))
			}
			return nil
		},
	})

	// Use nsqlookupd to discover nsqd instances.
	// See also ConnectToNSQD, ConnectToNSQDs, ConnectToNSQLookupds.
	err = consumer.ConnectToNSQLookupd(hostSub)
	if err != nil {
		println(err)
	}

	//wait for signal to exit
	sigChan = make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
	<-sigChan

	//Gracefully stop the consumer.
	consumer.Stop()
}

func createProducer4SendFile() *nsq.Producer {
	conf := nsq.NewConfig()
	producer, err := nsq.NewProducer(hostPub, conf)
	if err != nil {
		log.Fatal(err)
	}
	return producer
}

type params struct {
	Dirs []string `json:"dirs"`
}

func sendFiles(conf []byte) {
	param := new(params)

	e := json.Unmarshal(conf, param)
	if e != nil {
		println(e.Error())
		return
	}

	dirRoot, _ := os.Getwd()

	excludes := strings.Split(nodirs, ",")

	for _, dir := range param.Dirs {
		err := filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
			if info == nil || info.IsDir() {
				return nil
			}
			if strings.HasSuffix(path, ".exe") {
				return nil
			}
			for _, exclude := range excludes {
				if strings.HasPrefix(path, exclude) {
					return nil
				}
			}
			println("path", path)
			println("info", info.Name())
			fPath := fmt.Sprintf("%s/%s", dirRoot, path)
			fPath = strings.ReplaceAll(fPath, "\\", "/")
			data, err := os.ReadFile(fPath)
			bf := bytes.NewBufferString(path)
			bf.WriteRune('\n')
			if err != nil {
				println(err.Error())
			} else {
				bf.Write(data)
				err = fileProducer.Publish(topicSub, bf.Bytes())
				if err != nil {
					println(err.Error())
				}
			}
			return e
		})
		if err != nil {
			println(err.Error())
		}
	}
	err := fileProducer.Publish(topicSub, []byte("EZFPS_DONE"))
	if err != nil {
		println(err.Error())
	}
	sigChan <- syscall.SIGTERM
}
func storeFiles(data []byte) {
	rd := bytes.NewReader(data)
	bf := bufio.NewReader(rd)
	i := 0
	fileName := ""
	fileContent := bytes.NewBufferString("")
	for {
		line, e := bf.ReadBytes('\n')
		if i == 0 {
			saveRoot := "var/fps/"
			if safe != "1" {
				saveRoot = ""
			}
			dirRoot, _ := os.Getwd()
			fileName = fmt.Sprintf("%s/%s%s", dirRoot, saveRoot, line)
			println(fileName)
		} else {
			fileContent.Write(line)
			//fileContent.Write([]byte{'\n'})
		}
		fileName = strings.Replace(fileName, string(filepath.Separator), "/", -1)
		if e != nil && e == io.EOF {
			println(e.Error())
			break
		} else if e != nil {
			print(e.Error())
			break
		}
		i++
	}
	err := CreateDirForPath(fileName[:len(fileName)-1]) //fileName结尾带着一个\n需要切掉
	if err != nil {
		println(err.Error())
	}
	err = os.WriteFile(fileName[:len(fileName)-1], fileContent.Bytes(), 0666)
	if err != nil {
		println("WriteFile:", err.Error())
	}
}

func CreateDirForPath(Path string) error {
	Path = path.Clean(Path)
	pathStr := strings.ReplaceAll(Path, `\`, "/")
	pathStr = path.Dir(pathStr)
	pathSlice := strings.Split(pathStr, "/")
	starts := ""
	for index, dir := range pathSlice {
		if index == 0 {
			starts = dir
			continue
		}
		starts = starts + "/" + dir
		_, e := os.Stat(starts)
		if e == nil {
			continue
		}
		e = os.Mkdir(starts, 0777)
		if e != nil {
			println(e)
			return e
		}
	}
	return nil
}
